{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "fbb70e9a",
   "metadata": {
    "toc": true
   },
   "source": [
    "<h1>Table of Contents<span class=\"tocSkip\"></span></h1>\n",
    "<div class=\"toc\"><ul class=\"toc-item\"></ul></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86a4bd36",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "下面展示基于隐马尔科夫模型的序列标注监督学习的代码。这里以命名实体识别任务为例，所使用的数据是Books数据集。为简单起见，标签序列采用BIO格式。首先构建数据集和标签集合："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "41673bbb",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-19T05:27:54.793516Z",
     "start_time": "2025-04-19T05:27:53.390376Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'B-NAME': 1, 'I-ORG': 2, 'B-PRO': 3, 'B-EDU': 4, 'I-NAME': 5, 'B-LOC': 6, 'B-TITLE': 7, 'B-RACE': 8, 'I-TITLE': 9, 'I-RACE': 10, 'I-PRO': 11, 'B-CONT': 12, 'I-EDU': 13, 'I-LOC': 14, 'I-CONT': 15, 'B-ORG': 16, 'O': 0}\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import json\n",
    "from collections import defaultdict\n",
    "\n",
    "\n",
    "class NERDataset:\n",
    "    def __init__(self):\n",
    "        train_file, test_file, label_file = 'ner_train.jsonl',\\\n",
    "            'ner_test.jsonl', 'ner_labels.json'\n",
    "        \n",
    "        def read_file(file_name):\n",
    "            with open(file_name, 'r', encoding='utf-8') as fin:\n",
    "                json_list = list(fin)\n",
    "            data_split = []\n",
    "            for json_str in json_list:\n",
    "                raw = json.loads(json_str)\n",
    "                d = {'tokens': raw['tokens'], 'tags': raw['tags']}\n",
    "                data_split.append(d)\n",
    "            return data_split\n",
    "        \n",
    "        # 读取JSON文件，转化为Python对象\n",
    "        self.train_data, self.test_data = read_file(train_file),\\\n",
    "            read_file(test_file)\n",
    "        self.label2id = json.loads(open(label_file, 'r').read())\n",
    "        self.id2label = {}\n",
    "        for k, v in self.label2id.items():\n",
    "            self.id2label[v] = k\n",
    "        print(self.label2id)\n",
    "\n",
    "    # 建立词表，过滤低频词\n",
    "    def build_vocab(self, min_freq=3):\n",
    "        # 统计词频\n",
    "        frequency = defaultdict(int)\n",
    "        for data in self.train_data:\n",
    "            tokens = data['tokens']\n",
    "            for token in tokens:\n",
    "                frequency[token] += 1\n",
    "                \n",
    "        print(f'unique tokens = {len(frequency)}, '+\\\n",
    "              f'total counts = {sum(frequency.values())}, '+\\\n",
    "              f'max freq = {max(frequency.values())}, '+\\\n",
    "              f'min freq = {min(frequency.values())}')\n",
    "        \n",
    "        # 由于词与标签一一对应，不能随便删除字符，\n",
    "        # 因此加入<unk>用于替代未登录词，加入<pad>用于批量训练\n",
    "        self.token2id = {'<pad>': 0, '<unk>': 1}\n",
    "        self.id2token = {0: '<pad>', 1: '<unk>'}\n",
    "        total_count = 0\n",
    "        # 将高频词加入词表\n",
    "        for token, freq in sorted(frequency.items(),\\\n",
    "                key=lambda x: -x[1]):\n",
    "            if freq > min_freq:\n",
    "                self.token2id[token] = len(self.token2id)\n",
    "                self.id2token[len(self.id2token)] = token\n",
    "                total_count += freq\n",
    "            else:\n",
    "                break\n",
    "        print(f'min_freq = {min_freq}, '\n",
    "              f'remaining tokens = {len(self.token2id)}, '\n",
    "              f'in-vocab rate = {total_count / sum(frequency.values())}')\n",
    "\n",
    "    # 将文本输入转化为词表中对应的索引\n",
    "    def convert_tokens_to_ids(self):\n",
    "        for data_split in [self.train_data, self.test_data]:\n",
    "            for data in data_split:\n",
    "                data['token_ids'] = []\n",
    "                for token in data['tokens']:\n",
    "                    data['token_ids'].append(self.token2id.get(token, 1)) \n",
    "        \n",
    "dataset = NERDataset()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25650a7c",
   "metadata": {},
   "source": [
    "接下来建立词表，将词元转换为索引，并将数据转化为适合训练的格式。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "a3eb49e5",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-19T05:27:54.917536Z",
     "start_time": "2025-04-19T05:27:54.797521Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "unique tokens = 2454, total counts = 182068, max freq = 5630, min freq = 1\n",
      "min_freq = 0, remaining tokens = 2456, in-vocab rate = 1.0\n",
      "[595, 510, 353, 263, 424, 225, 764, 637, 353, 65, 80, 47, 41, 74, 53, 41, 141, 23, 74, 7, 259, 27, 135, 47, 31, 74, 67, 25, 1605, 815, 1157, 225, 595, 1158, 738, 688, 353, 1025, 65, 234, 27, 135, 316, 41, 31, 7, 80, 41, 42, 31, 47, 27, 528, 41, 141, 27, 135, 67, 1005, 1606, 363, 19, 677, 294, 37, 678, 1756, 604, 873, 1006, 1005, 353, 33, 30, 9, 113, 5, 45, 11, 82, 60, 21, 26, 5, 186, 153, 100, 565, 873, 353, 424, 1993, 387, 346, 250, 13, 5, 102, 214, 25, 11, 82, 60, 21, 26, 5, 186, 2, 7, 459, 97, 89, 147, 140, 139, 69, 46, 21, 12, 2, 7, 964, 98, 240, 677, 59, 9, 103, 11, 82, 60, 335, 200, 76, 26, 6, 110, 20, 9, 15, 4, 122, 539, 241, 99, 621, 40, 200, 57, 82, 156, 25, 11, 82, 381, 371, 91, 202, 6, 52, 2, 7, 670, 100, 84, 204, 53, 120, 27, 42, 3, 56, 1159, 3, 329, 31, 265, 31, 3, 56, 394, 394, 3, 84, 357, 84, 7, 25, 397, 7, 41, 7, 74, 7, 135, 7, 31, 7, 87, 7, 259, 7, 31, 7, 74, 7, 41, 7, 131, 7, 28, 289, 385, 4]\n",
      "['阿', '里', '斯', '提', '德', '·', '波', '拉', '斯', '（', 'A', 'r', 'i', 's', 't', 'i', 'd', 'e', 's', ' ', 'B', 'o', 'u', 'r', 'a', 's', '）', '和', '卢', '卡', '雅', '·', '阿', '伊', '纳', '罗', '斯', '托', '（', 'L', 'o', 'u', 'k', 'i', 'a', ' ', 'A', 'i', 'n', 'a', 'r', 'o', 'z', 'i', 'd', 'o', 'u', '）', '夫', '妇', '二', '人', '均', '拥', '有', '希', '腊', '比', '雷', '埃', '夫', '斯', '技', '术', '教', '育', '学', '院', '计', '算', '机', '工', '程', '学', '位', '以', '及', '色', '雷', '斯', '德', '谟', '克', '利', '特', '大', '学', '电', '子', '和', '计', '算', '机', '工', '程', '学', '位', '，', ' ', '都', '从', '事', '过', '软', '件', '开', '发', '工', '作', '，', ' ', '且', '目', '前', '均', '为', '教', '授', '计', '算', '机', '相', '关', '课', '程', '的', '高', '中', '教', '师', '。', '他', '们', '写', '了', '很', '多', '关', '于', '算', '法', '和', '计', '算', '思', '维', '方', '面', '的', '书', '，', ' ', '涉', '及', 'P', 'y', 't', 'h', 'o', 'n', '、', 'C', '#', '、', 'J', 'a', 'v', 'a', '、', 'C', '+', '+', '、', 'P', 'H', 'P', ' ', '和', 'V', ' ', 'i', ' ', 's', ' ', 'u', ' ', 'a', ' ', 'l', ' ', 'B', ' ', 'a', ' ', 's', ' ', 'i', ' ', 'c', ' ', '等', '语', '言', '。']\n"
     ]
    }
   ],
   "source": [
    "# 截取一部分数据便于更快训练\n",
    "dataset.train_data = dataset.train_data[:1000]\n",
    "dataset.test_data = dataset.test_data[:200]\n",
    "\n",
    "dataset.build_vocab(min_freq=0)\n",
    "dataset.convert_tokens_to_ids()\n",
    "print(dataset.train_data[0]['token_ids'])\n",
    "print([dataset.id2token[token_id] for token_id in \\\n",
    "       dataset.train_data[0]['token_ids']])\n",
    "\n",
    "def collect_data(data_split):\n",
    "    X, Y = [], []\n",
    "    for data in data_split:\n",
    "        assert len(data['token_ids']) == len(data['tags'])\n",
    "        X.append(data['token_ids'])\n",
    "        Y.append(data['tags'])\n",
    "    return X, Y\n",
    "\n",
    "train_X, train_Y = collect_data(dataset.train_data)\n",
    "test_X, test_Y = collect_data(dataset.test_data)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7de17192",
   "metadata": {},
   "source": [
    "随后实现隐马尔科夫模型，使用最大似然估计得到模型参数，使用维特比算法进行解码。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "3229fd6a",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-19T05:27:55.322542Z",
     "start_time": "2025-04-19T05:27:54.920539Z"
    },
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "class HMM:\n",
    "    def __init__(self, n_tags, n_tokens):\n",
    "        self.n_tags = n_tags\n",
    "        self.n_tokens = n_tokens\n",
    "    \n",
    "    # 使用最大似然估计计算模型参数\n",
    "    def fit(self, X, Y):\n",
    "        Y0_cnt = np.zeros(self.n_tags)\n",
    "        YY_cnt = np.zeros((self.n_tags, self.n_tags))\n",
    "        YX_cnt = np.zeros((self.n_tags, self.n_tokens))\n",
    "        for x, y in zip(X, Y):\n",
    "            Y0_cnt[y[0]] += 1\n",
    "            last_y = y[0]\n",
    "            for i in range(1, len(y)):\n",
    "                YY_cnt[last_y, y[i]] += 1\n",
    "                last_y = y[i]\n",
    "            for xi, yi in zip(x, y):\n",
    "                YX_cnt[yi, xi] += 1\n",
    "        self.init_prob = Y0_cnt / Y0_cnt.sum()\n",
    "        self.transition_prob = YY_cnt\n",
    "        self.emission_prob = YX_cnt\n",
    "        for i in range(self.n_tags):\n",
    "            # 为了避免训练集过小时除0\n",
    "            yy_sum = YY_cnt[i].sum()\n",
    "            if yy_sum > 0:\n",
    "                self.transition_prob[i] = YY_cnt[i] / yy_sum\n",
    "            yx_sum = YX_cnt[i].sum()\n",
    "            if yx_sum > 0:\n",
    "                self.emission_prob[i] = YX_cnt[i] / yx_sum\n",
    "    \n",
    "    # 已知模型参数的条件下，使用维特比算法解码得到最优标签序列\n",
    "    def viterbi(self, x):\n",
    "        assert hasattr(self, 'init_prob') and hasattr(self,\\\n",
    "            'transition_prob') and hasattr(self, 'emission_prob')\n",
    "        Pi = np.zeros((len(x), self.n_tags))\n",
    "        Y = np.zeros((len(x), self.n_tags), dtype=np.int32)\n",
    "        # 初始化\n",
    "        for i in range(self.n_tags):\n",
    "            Pi[0, i] = self.init_prob[i] * self.emission_prob[i, x[0]]\n",
    "            Y[0, i] = -1\n",
    "        for t in range(1, len(x)):\n",
    "            for i in range(self.n_tags):\n",
    "                tmp = []\n",
    "                for j in range(self.n_tags):\n",
    "                    tmp.append(self.transition_prob[j, i] * Pi[t-1, j])\n",
    "                best_j = np.argmax(tmp)\n",
    "                # 维特比算法递推公式\n",
    "                Pi[t, i] = self.emission_prob[i, x[t]] * tmp[best_j]\n",
    "                Y[t, i] = best_j\n",
    "        y = [np.argmax(Pi[-1])]\n",
    "        for t in range(len(x)-1, 0, -1):\n",
    "            y.append(Y[t, y[-1]])\n",
    "        return np.max(Pi[len(x)-1]), y[::-1]\n",
    "    \n",
    "    def decode(self, X):\n",
    "        Y = []\n",
    "        for x in X:\n",
    "            _, y = self.viterbi(x)\n",
    "            Y.append(y)\n",
    "        return Y\n",
    "\n",
    "hmm = HMM(len(dataset.label2id), len(dataset.token2id))\n",
    "hmm.fit(train_X, train_Y)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "32868ed6",
   "metadata": {},
   "source": [
    "最后验证模型效果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "4f4b7e88",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-19T05:29:27.916607Z",
     "start_time": "2025-04-19T05:27:55.326608Z"
    },
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "precision = 0.45762149610217284, recall = 0.3289222699093944, f1 = 0.38274259554692375\n",
      "precision = 0.4189636163175303, recall = 0.23270055113288426, f1 = 0.2992125984251969\n"
     ]
    }
   ],
   "source": [
    "def extract_entity(labels):\n",
    "    entity_list = []\n",
    "    entity_start = -1\n",
    "    entity_length = 0\n",
    "    entity_type = None\n",
    "    for token_index, label in enumerate(labels):\n",
    "        if label.startswith('B'):\n",
    "            if entity_start != -1:\n",
    "                # 遇到了一个新的B，将上一个实体加入列表\n",
    "                entity_list.append((entity_start, entity_length,\\\n",
    "                    entity_type))\n",
    "            # 记录新实体\n",
    "            entity_start = token_index\n",
    "            entity_length = 1\n",
    "            entity_type = label.split('-')[1]\n",
    "        elif label.startswith('I'):\n",
    "            if entity_start != -1:\n",
    "                # 上一个实体未关闭，遇到了一个新的I\n",
    "                if entity_type == label.split('-')[1]:\n",
    "                    # 若上一个实体与当前类型相同，长度+1\n",
    "                    entity_length += 1\n",
    "                else:\n",
    "                    # 若上一个实体与当前类型不同，\n",
    "                    # 将上一个实体加入列表，重置实体\n",
    "                    entity_list.append((entity_start, entity_length,\\\n",
    "                        entity_type))\n",
    "                    entity_start = -1\n",
    "                    entity_length = 0\n",
    "                    entity_type = None\n",
    "        else:\n",
    "            if entity_start != -1:\n",
    "                # 遇到了一个新的O，将上一个实体加入列表\n",
    "                entity_list.append((entity_start, entity_length,\\\n",
    "                    entity_type))\n",
    "            # 重置实体\n",
    "            entity_start = -1\n",
    "            entity_length = 0\n",
    "            entity_type = None\n",
    "    if entity_start != -1:\n",
    "        # 将上一个实体加入列表\n",
    "        entity_list.append((entity_start, entity_length, entity_type))\n",
    "    return entity_list\n",
    "\n",
    "def compute_metric(Y, P):\n",
    "    true_entity_set = set()\n",
    "    pred_entity_set = set()\n",
    "    for sent_no, labels in enumerate(Y):\n",
    "        labels = [dataset.id2label[x] for x in labels]\n",
    "        for ent in extract_entity(labels):\n",
    "            true_entity_set.add((sent_no, ent))\n",
    "    for sent_no, labels in enumerate(P):\n",
    "        labels = [dataset.id2label[x] for x in labels]\n",
    "        for ent in extract_entity(labels):\n",
    "            pred_entity_set.add((sent_no, ent))\n",
    "    if len(true_entity_set) > 0:\n",
    "        recall = len(true_entity_set & pred_entity_set)\\\n",
    "            / len(true_entity_set)\n",
    "    else:\n",
    "        recall = 0\n",
    "    if len(pred_entity_set) > 0:\n",
    "        precision = len(true_entity_set & pred_entity_set)\\\n",
    "            / len(pred_entity_set)\n",
    "    else:\n",
    "        precision = 0\n",
    "    if precision > 0 and recall > 0:\n",
    "        f1 = 2 * precision * recall / (precision + recall)\n",
    "    else:\n",
    "        f1 = 0\n",
    "    return precision, recall, f1\n",
    "\n",
    "train_P = hmm.decode(train_X)\n",
    "p, r, f = compute_metric(train_Y, train_P)\n",
    "print(f'precision = {p}, recall = {r}, f1 = {f}')\n",
    "test_P = hmm.decode(test_X)\n",
    "p, r, f = compute_metric(test_Y, test_P)\n",
    "print(f'precision = {p}, recall = {r}, f1 = {f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3abe8397",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "下面展示基于条件随机场的序列标注模型。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "787f8ad1",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-19T05:29:37.169714Z",
     "start_time": "2025-04-19T05:29:27.921701Z"
    }
   },
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "代码修改自GitHub项目kmkurn/pytorch-crf\n",
    "（Copyright (c) 2019, Kemal Kurniawan, MIT License（见附录））\n",
    "\"\"\"\n",
    "import torch\n",
    "from torch import nn\n",
    "\n",
    "class CRFLayer(nn.Module):\n",
    "    def __init__(self, n_tags, n_features):\n",
    "        super().__init__()\n",
    "        self.n_tags = n_tags\n",
    "        self.n_features = n_features\n",
    "        # 定义模型参数\n",
    "        self.transitions = nn.Parameter(torch.empty(\\\n",
    "            n_tags, n_tags))\n",
    "        self.emission_weight = nn.Parameter(torch.empty(\\\n",
    "            n_features, n_tags))\n",
    "        self.start_transitions = nn.Parameter(torch.empty(n_tags))\n",
    "        self.end_transitions = nn.Parameter(torch.empty(n_tags))\n",
    "        self.reset_parameters()\n",
    "        \n",
    "    # 使用（-0.1,0.1）之间的均匀分布初始化参数\n",
    "    def reset_parameters(self):\n",
    "        nn.init.uniform_(self.transitions, -0.1, 0.1)\n",
    "        nn.init.uniform_(self.emission_weight, -0.1, 0.1)\n",
    "        nn.init.uniform_(self.start_transitions, -0.1, 0.1)\n",
    "        nn.init.uniform_(self.end_transitions, -0.1, 0.1)\n",
    "    \n",
    "    # 使用动态规划计算得分\n",
    "    def compute_score(self, emissions, tags, masks):\n",
    "        seq_len, batch_size, n_tags = emissions.shape\n",
    "        score = self.start_transitions[tags[0]] + \\\n",
    "            emissions[0, torch.arange(batch_size), tags[0]]\n",
    "        \n",
    "        for i in range(1, seq_len):\n",
    "            score += self.transitions[tags[i-1], tags[i]] * masks[i]\n",
    "            score += emissions[i, torch.arange(batch_size),\\\n",
    "                tags[i]] * masks[i]\n",
    "        \n",
    "        seq_ends = masks.long().sum(dim=0) - 1\n",
    "        last_tags = tags[seq_ends, torch.arange(batch_size)]\n",
    "        score += self.end_transitions[last_tags]\n",
    "        return score\n",
    "    \n",
    "    # 计算配分函数\n",
    "    def computer_normalizer(self, emissions, masks):\n",
    "        seq_len, batch_size, n_tags = emissions.shape\n",
    "        # batch_size * n_tags, [起始分数 + y_0为某标签的发射分数 ...]\n",
    "        score = self.start_transitions + emissions[0]\n",
    "        \n",
    "        for i in range(1, seq_len):\n",
    "            # batch_size * n_tags * 1 [y_{i-1}为某tag的总分]\n",
    "            broadcast_score = score.unsqueeze(2)\n",
    "            # batch_size * 1 * n_tags [y_i为某标签的发射分数]\n",
    "            broadcast_emissions = emissions[i].unsqueeze(1)\n",
    "            # batch_size * n_tags * n_tags [任意y_{i-1}到y_i的总分]\n",
    "            next_score = broadcast_score + self.transitions +\\\n",
    "                broadcast_emissions\n",
    "            # batch_size * n_tags [对y_{i-1}求和]\n",
    "            next_score = torch.logsumexp(next_score, dim=1)\n",
    "            # masks为True则更新，否则保留\n",
    "            score = torch.where(masks[i].unsqueeze(1), next_score, score)\n",
    "            \n",
    "        score += self.end_transitions\n",
    "        return torch.logsumexp(score, dim=1)\n",
    "    \n",
    "    def forward(self, features, tags, masks):\n",
    "        \"\"\"\n",
    "        features: seq_len * batch_size * n_features\n",
    "        tags/masks: seq_len * batch_size\n",
    "        \"\"\"\n",
    "        _, batch_size, _ = features.size()\n",
    "        emissions = torch.matmul(features, self.emission_weight)\n",
    "        masks = masks.to(torch.bool)\n",
    "        \n",
    "        score = self.compute_score(emissions, tags, masks)\n",
    "        partition = self.computer_normalizer(emissions, masks)\n",
    "        \n",
    "        likelihood = score - partition\n",
    "        return likelihood.sum() / batch_size\n",
    "    \n",
    "    def decode(self, features, masks):\n",
    "        # 与computer_normalizer类似，sum变为max\n",
    "        emissions = torch.matmul(features, self.emission_weight)\n",
    "        masks = masks.to(torch.bool)\n",
    "        \n",
    "        seq_len, batch_size, n_tags = emissions.shape\n",
    "        score = self.start_transitions + emissions[0]\n",
    "        history = []\n",
    "        \n",
    "        for i in range(1, seq_len):\n",
    "            broadcast_score = score.unsqueeze(2)\n",
    "            broadcast_emission = emissions[i].unsqueeze(1)\n",
    "            \n",
    "            next_score = broadcast_score + self.transitions +\\\n",
    "                broadcast_emission\n",
    "            next_score, indices = next_score.max(dim=1)\n",
    "            \n",
    "            score = torch.where(masks[i].unsqueeze(1), next_score,\\\n",
    "                score)\n",
    "            history.append(indices)\n",
    "            \n",
    "        score += self.end_transitions\n",
    "        seq_ends = masks.long().sum(dim=0) - 1\n",
    "        best_tags_list = []\n",
    "        \n",
    "        for idx in range(batch_size):\n",
    "            _, best_last_tag = score[idx].max(dim=0)\n",
    "            best_tags = [best_last_tag.item()]\n",
    "            \n",
    "            for hist in reversed(history[:seq_ends[idx]]):\n",
    "                best_last_tag = hist[idx][best_tags[-1]]\n",
    "                best_tags.append(best_last_tag.item())\n",
    "                \n",
    "            best_tags.reverse()\n",
    "            best_tags_list.append(best_tags)\n",
    "            \n",
    "        return best_tags_list"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "69f4af96",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-19T05:29:37.189623Z",
     "start_time": "2025-04-19T05:29:37.175228Z"
    }
   },
   "outputs": [],
   "source": [
    "class CRF(nn.Module):\n",
    "    def __init__(self, vocab_size, hidden_size, n_tags):\n",
    "        super().__init__()\n",
    "        self.embedding = nn.Embedding(vocab_size, hidden_size)\n",
    "        self.crf = CRFLayer(n_tags, hidden_size)\n",
    "        \n",
    "    def forward(self, input_ids, masks, labels):\n",
    "        \"\"\"\n",
    "        input_ids/masks/labels: batch_size * seq_len\n",
    "        \"\"\"\n",
    "        # 将输入序列转化为词嵌入序列\n",
    "        # batch_size * seq_len * embed_size\n",
    "        embed = self.embedding(input_ids)\n",
    "        embed = torch.transpose(embed, 0, 1)\n",
    "        masks = torch.transpose(masks, 0, 1)\n",
    "        labels = torch.transpose(labels, 0, 1)\n",
    "        # 隐状态和标签、掩码输入条件随机场计算损失\n",
    "        llh = self.crf(embed, labels, masks)\n",
    "        return -llh\n",
    "    \n",
    "    def decode(self, input_ids, masks):\n",
    "        embed = self.embedding(input_ids)\n",
    "        embed = torch.transpose(embed, 0, 1)\n",
    "        masks = torch.transpose(masks, 0, 1)\n",
    "        # 调用CRFLayer进行解码\n",
    "        return self.crf.decode(embed, masks)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "178948b4",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-19T05:35:01.990829Z",
     "start_time": "2025-04-19T05:29:37.192630Z"
    },
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "epoch-19, loss=47.97: 100%|█| 20/20 [05:18<00:00, 15.94s/it]\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkQAAAGwCAYAAABIC3rIAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAABWpUlEQVR4nO3deXxU1f3/8ddksm+TjSQEAgQIa0CQILIoWDZFQNQWBVRstWpBERW3n60VW0FRAYWKxVqxbvitAsUdUIkgKsgaFlkDCZAQlmSybzP390fISFgDmWRmkvfz8ZjHg7n3zMznEMm8Pefcc02GYRiIiIiINGJeri5ARERExNUUiERERKTRUyASERGRRk+BSERERBo9BSIRERFp9BSIREREpNFTIBIREZFGz9vVBXgKu93O4cOHCQkJwWQyubocERERqQHDMMjPzycuLg4vr3OPAykQ1dDhw4eJj493dRkiIiJyCTIyMmjevPk5zysQ1VBISAhQ+RcaGhrq4mpERESkJvLy8oiPj3d8j5+LAlENVU2ThYaGKhCJiIh4mAstd9GiahEREWn0FIhERESk0VMgEhERkUZPa4hEREQAm81GeXm5q8uQi+Tj44PZbK71+ygQiYhIo2YYBllZWeTm5rq6FLlEYWFhxMbG1mqfQAUiERFp1KrCUHR0NIGBgdp814MYhkFRURHZ2dkANG3a9JLfS4FIREQaLZvN5ghDkZGRri5HLkFAQAAA2dnZREdHX/L0mRZVi4hIo1W1ZigwMNDFlUhtVP38arMGTIFIREQaPU2TeTZn/PwUiERERKTRUyASERGRRk+BSERERGjVqhWzZ892+Xu4iq4yc7HiMhuZ1mKiQvwI9fdxdTkiIuIhBgwYQLdu3ZwWQNatW0dQUJBT3ssTaYTIxcb+60d+83IK3+8+5upSRESkgTEMg4qKihq1bdKkSaO+2k6ByMXiLJX7Jxy2lri4EhERgZOb/ZVVuORhGEaNarzzzjtJSUnhlVdewWQyYTKZ2L9/PytXrsRkMvHVV1+RnJyMn58fq1atYu/evdxwww3ExMQQHBxMz549WbFiRbX3PH26y2Qy8a9//Ysbb7yRwMBAEhMTWbp06UX9Xaanp3PDDTcQHBxMaGgoo0eP5siRI47zmzdv5pprriEkJITQ0FB69OjBzz//DMCBAwcYMWIE4eHhBAUF0blzZz7//POL+vyLoSkzF2tq8Qcgy1rs4kpERASguNxGp6e/cslnb392KIG+F/5qfuWVV9i1axdJSUk8++yzQOUIz/79+wF47LHHeOmll2jdujVhYWEcPHiQYcOG8fe//x1/f3/efvttRowYwc6dO2nRosU5P2fq1KnMmDGDF198kTlz5jBu3DgOHDhARETEBWs0DINRo0YRFBRESkoKFRUVTJgwgVtuuYWVK1cCMG7cOLp37868efMwm81s2rQJH5/K5SMTJ06krKyM7777jqCgILZv305wcPAFP/dSKRC5WNMwjRCJiMjFsVgs+Pr6EhgYSGxs7Bnnn332WQYPHux4HhkZyWWXXeZ4/ve//53FixezdOlS7r///nN+zp133smYMWMAmDZtGnPmzGHt2rVce+21F6xxxYoVbNmyhbS0NOLj4wF455136Ny5M+vWraNnz56kp6fz6KOP0qFDBwASExMdr09PT+fmm2+mS5cuALRu3fqCn1kbCkQuVjVClJmrESIREXcQ4GNm+7NDXfbZzpCcnFzteWFhIVOnTuXTTz/l8OHDVFRUUFxcTHp6+nnfp2vXro4/BwUFERIS4rhv2IXs2LGD+Ph4RxgC6NSpE2FhYezYsYOePXvy8MMPc/fdd/POO+8waNAgfve739GmTRsAJk2axJ/+9CeWLVvGoEGDuPnmm6vV42xaQ+RijkCkESIREbdgMpkI9PV2ycNZO2affrXYo48+yscff8xzzz3HqlWr2LRpE126dKGsrOy871M1fXXq343dbq9RDYZhnLU/px5/5pln2LZtG9dffz3ffPMNnTp1YvHixQDcfffd7Nu3j9tvv53U1FSSk5OZM2dOjT77UigQuVjcySmz7PxSKmw1+49MRETE19cXm81Wo7arVq3izjvv5MYbb6RLly7ExsY61hvVlU6dOpGenk5GRobj2Pbt27FarXTs2NFxrF27djz00EMsW7aMm266ibfeestxLj4+nvvuu49FixbxyCOP8MYbb9RZvQpELhYV7Ie3lwmb3eBoQamryxEREQ/RqlUrfvrpJ/bv38+xY8fOO3LTtm1bFi1axKZNm9i8eTNjx46t8UjPpRo0aBBdu3Zl3LhxbNiwgbVr13LHHXfQv39/kpOTKS4u5v7772flypUcOHCA77//nnXr1jnC0uTJk/nqq69IS0tjw4YNfPPNN9WC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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "from torch.optim import SGD, Adam\n",
    "from copy import deepcopy\n",
    "\n",
    "# 将数据适配为PyTorch数据集\n",
    "class SeqLabelDataset(Dataset):\n",
    "    def __init__(self, tokens, labels):\n",
    "        self.tokens = deepcopy(tokens)\n",
    "        self.labels = deepcopy(labels)\n",
    "        \n",
    "    def __getitem__(self, idx):\n",
    "        return self.tokens[idx], self.labels[idx]\n",
    "    \n",
    "    def __len__(self):\n",
    "        return len(self.labels)\n",
    "    \n",
    "    # 将每个批次转化为PyTorch张量\n",
    "    @classmethod\n",
    "    def collate_batch(cls, inputs):\n",
    "        input_ids, labels, masks = [], [], []\n",
    "        max_len = -1\n",
    "        for ids, tags in inputs:\n",
    "            input_ids.append(ids)\n",
    "            labels.append(tags)\n",
    "            masks.append([1] * len(tags))\n",
    "            max_len = max(max_len, len(ids))\n",
    "        for ids, tags, msks in zip(input_ids, labels, masks):\n",
    "            pad_len = max_len - len(ids)\n",
    "            ids.extend([0] * pad_len)\n",
    "            tags.extend([0] * pad_len)\n",
    "            msks.extend([0] * pad_len)\n",
    "        input_ids = torch.tensor(np.array(input_ids),\\\n",
    "            dtype=torch.long)\n",
    "        labels = torch.tensor(np.array(labels), dtype=torch.long)\n",
    "        masks = torch.tensor(np.array(masks), dtype=torch.uint8)\n",
    "        return {'input_ids': input_ids, 'masks': masks,\\\n",
    "                'labels': labels}\n",
    "    \n",
    "# 准备数据\n",
    "train_dataset = SeqLabelDataset(train_X, train_Y)\n",
    "test_dataset = SeqLabelDataset(test_X, test_Y)\n",
    "\n",
    "from tqdm import tqdm, trange\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def train(model, batch_size, epochs, learning_rate):\n",
    "    train_dataloader = DataLoader(train_dataset,\n",
    "        batch_size=batch_size, shuffle=True, \n",
    "        collate_fn=SeqLabelDataset.collate_batch)\n",
    "    optimizer = Adam(model.parameters(), lr=learning_rate)\n",
    "    model.zero_grad()\n",
    "    tr_step = []\n",
    "    tr_loss = []\n",
    "    global_step = 0\n",
    "    \n",
    "    with trange(epochs, desc='epoch', ncols=60) as pbar:\n",
    "        for epoch in pbar:\n",
    "            model.train()\n",
    "            for step, batch in enumerate(train_dataloader):\n",
    "                global_step += 1\n",
    "                loss = model(**batch)\n",
    "                loss.backward()\n",
    "                optimizer.step()\n",
    "                model.zero_grad()\n",
    "                pbar.set_description(f'epoch-{epoch}, '+\\\n",
    "                    f'loss={loss.item():.2f}')\n",
    "                \n",
    "                if epoch > 0:\n",
    "                    tr_step.append(global_step)\n",
    "                    tr_loss.append(loss.item())\n",
    "\n",
    "    # 打印损失曲线\n",
    "    plt.plot(tr_step, tr_loss, label='train loss')\n",
    "    plt.xlabel('training steps')\n",
    "    plt.ylabel('loss')\n",
    "    plt.legend()\n",
    "    plt.show()\n",
    "                \n",
    "vocab_size = len(dataset.token2id)\n",
    "hidden_size = 32\n",
    "n_tags = len(dataset.label2id)\n",
    "crf = CRF(vocab_size, hidden_size, n_tags)\n",
    "batch_size = 128\n",
    "epochs = 20\n",
    "learning_rate = 1e-2\n",
    "train(crf, batch_size, epochs, learning_rate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "4a1e7e25",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-19T05:35:09.542346Z",
     "start_time": "2025-04-19T05:35:01.994341Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "precision = 0.5443694862480539, recall = 0.2501192179303767, f1 = 0.34275445188694653\n",
      "precision = 0.4992887624466572, recall = 0.21494182486221677, f1 = 0.300513698630137\n"
     ]
    }
   ],
   "source": [
    "# 验证效果\n",
    "\n",
    "def evaluate(X, Y, model, batch_size):\n",
    "    dataset = SeqLabelDataset(X, Y)\n",
    "    dataloader = DataLoader(dataset, batch_size=batch_size,\\\n",
    "        collate_fn=SeqLabelDataset.collate_batch)\n",
    "    model.eval()\n",
    "    with torch.no_grad():\n",
    "        P = []\n",
    "        for batch in dataloader:\n",
    "            preds = model.decode(batch['input_ids'], batch['masks'])\n",
    "            P.extend(preds)\n",
    "\n",
    "    p, r, f = compute_metric(Y, P)\n",
    "    print(f'precision = {p}, recall = {r}, f1 = {f}')\n",
    "    \n",
    "evaluate(train_X, train_Y, crf, batch_size)\n",
    "evaluate(test_X, test_Y, crf, batch_size)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc369a17",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "这里介绍基于双向长短期记忆-条件随机场（BiLSTM-CRF）结构的代码实现。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "990c5bc1",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-19T05:35:09.559734Z",
     "start_time": "2025-04-19T05:35:09.545864Z"
    }
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "\n",
    "# 定义模型，将长短期记忆的输出输入给条件随机场，\n",
    "# 利用条件随机场计算损失和解码\n",
    "class LSTM_CRF(nn.Module):\n",
    "    def __init__(self, vocab_size, hidden_size, num_layers,\\\n",
    "                 dropout, n_tags):\n",
    "        super().__init__()\n",
    "        self.embedding = nn.Embedding(vocab_size, hidden_size)\n",
    "        self.lstm = nn.LSTM(input_size=hidden_size,\n",
    "                            hidden_size=hidden_size,\n",
    "                            num_layers=num_layers,\n",
    "                            batch_first=False,\n",
    "                            dropout=dropout,\n",
    "                            bidirectional=True)\n",
    "        self.crf = CRFLayer(n_tags, hidden_size * 2)\n",
    "    \n",
    "    def forward(self, input_ids, masks, labels):\n",
    "        \"\"\"\n",
    "        input_ids/masks/labels: batch_size * seq_len\n",
    "        \"\"\"\n",
    "        # 将输入序列转化为词嵌入序列\n",
    "        # batch_size * seq_len * embed_size\n",
    "        embed = self.embedding(input_ids)\n",
    "        embed = torch.transpose(embed, 0, 1)\n",
    "        masks = torch.transpose(masks, 0, 1)\n",
    "        labels = torch.transpose(labels, 0, 1)\n",
    "        # 输入长短期记忆得到隐状态\n",
    "        hidden_states, _ = self.lstm(embed)\n",
    "        # 隐状态和标签、掩码输入条件随机场计算损失\n",
    "        llh = self.crf(hidden_states, labels, masks)\n",
    "        return -llh\n",
    "    \n",
    "    def decode(self, input_ids, masks):\n",
    "        # 输入长短期记忆得到隐状态\n",
    "        embed = self.embedding(input_ids)\n",
    "        embed = torch.transpose(embed, 0, 1)\n",
    "        masks = torch.transpose(masks, 0, 1)\n",
    "        hidden_states, _ = self.lstm(embed)\n",
    "        # 调用条件随机场进行解码\n",
    "        return self.crf.decode(hidden_states, masks)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "ab41a9f0",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-19T05:42:07.664680Z",
     "start_time": "2025-04-19T05:35:09.564739Z"
    },
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "epoch-19, loss=31.08: 100%|█| 20/20 [06:57<00:00, 20.88s/it]\n"
     ]
    },
    {
     "data": {
      "image/png": "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EREREY+msCMiIiIeTWFHREREPJrCjoiIiHg0hR0PMHXqVPr06UNwcDAxMTHccMMN7Nixw+0e0zSZMmUKCQkJ+Pv7M2jQILZs2dJILa5bU6dOxTAMJk6caL3maf09fPgwt99+O5GRkQQEBHDRRRexdu1a67on9be0tJQ//vGPtG7dGn9/f9q0acOzzz5LWVmZdc/52t8ffviBkSNHkpCQgGEYzJ071+16TfpVVFTEhAkTiIqKIjAwkFGjRnHo0KEG7EXNna6/JSUlPP7443Tr1o3AwEASEhK48847OXLkiNtneEp/T/Xb3/4WwzB444033F73tP5u27aNUaNGERoaSnBwMJdccgkHDhywrjdUfxV2PMCSJUsYP348K1euZMGCBZSWljJ06FDy8vKse1566SVee+01pk+fzurVq4mLi2PIkCHWuWDnq9WrV/PPf/6T7t27u73uSf3NyMjg0ksvxcfHh6+//pqtW7fy6quvEhYWZt3jSf198cUX+fvf/8706dPZtm0bL730Ei+//DLTpk2z7jlf+5uXl0ePHj2YPn16lddr0q+JEycyZ84cZs+ezbJly8jNzWXEiBE4HI6G6kaNna6/+fn5rFu3jqeeeop169bx6aefsnPnTkaNGuV2n6f0t6K5c+fy008/kZCQUOmaJ/V3z549XHbZZXTq1InFixfz888/89RTT+Hn52fd02D9NcXjpKenm4C5ZMkS0zRNs6yszIyLizNfeOEF657CwkIzNDTU/Pvf/95YzTxnOTk5Zvv27c0FCxaYAwcONB966CHTND2vv48//rh52WWXVXvd0/p73XXXmb/+9a/dXhs9erR5++23m6bpOf0FzDlz5ljPa9KvzMxM08fHx5w9e7Z1z+HDh00vLy/zm2++abC218ap/a3KqlWrTMDcv3+/aZqe2d9Dhw6ZzZo1Mzdv3my2bNnSfP31161rntbfm2++2fpzW5WG7K9GdjxQVlYWABEREQAkJyeTmprK0KFDrXvsdjsDBw5kxYoVjdLGujB+/Hiuu+46Bg8e7Pa6p/X3888/p3fv3vzyl78kJiaGnj178vbbb1vXPa2/l112Gd999x07d+4E4Oeff2bZsmVce+21gOf116Um/Vq7di0lJSVu9yQkJJCUlHRe990lKysLwzCsUUtP629ZWRl33HEHjz76KF27dq103ZP6W1ZWxrx58+jQoQPDhg0jJiaGfv36uU11NWR/FXY8jGmaTJo0icsuu4ykpCQAUlNTAYiNjXW7NzY21rp2vpk9ezbr1q1j6tSpla55Wn/37t3LjBkzaN++Pd9++y333XcfDz74IO+99x7gef19/PHHGTt2LJ06dcLHx4eePXsyceJExo4dC3hef11q0q/U1FR8fX0JDw+v9p7zVWFhIb///e+59dZbrcMiPa2/L774It7e3jz44INVXvek/qanp5Obm8sLL7zANddcw/z587nxxhsZPXo0S5YsARq2vzr13MM88MADbNy4kWXLllW6ZhiG23PTNCu9dj44ePAgDz30EPPnz3eb+z2Vp/S3rKyM3r178/zzzwPQs2dPtmzZwowZM7jzzjut+zylvx9//DEffPABH330EV27dmXDhg1MnDiRhIQE7rrrLus+T+nvqWrTr/O97yUlJdxyyy2UlZXxt7/97Yz3n4/9Xbt2LW+++Sbr1q0767afj/11LSi4/vrrefjhhwG46KKLWLFiBX//+98ZOHBgte+tj/5qZMeDTJgwgc8//5zvv/+e5s2bW6/HxcUBVErK6enplf4VeT5Yu3Yt6enp9OrVC29vb7y9vVmyZAl//etf8fb2tvrkKf2Nj4+nS5cubq917tzZWtHgaf99H330UX7/+99zyy230K1bN+644w4efvhhaxTP0/rrUpN+xcXFUVxcTEZGRrX3nG9KSkoYM2YMycnJLFiwwBrVAc/q79KlS0lPT6dFixbW31v79+/nkUceoVWrVoBn9TcqKgpvb+8z/t3VUP1V2PEApmnywAMP8Omnn7Jo0SJat27tdr1169bExcWxYMEC67Xi4mKWLFnCgAEDGrq55+zqq69m06ZNbNiwwXr07t2b2267jQ0bNtCmTRuP6u+ll15aaSuBnTt30rJlS8Dz/vvm5+fj5eX+V5PNZrP+pehp/XWpSb969eqFj4+P2z0pKSls3rz5vOy7K+js2rWLhQsXEhkZ6Xbdk/p7xx13sHHjRre/txISEnj00Uf59ttvAc/qr6+vL3369Dnt310N2t86LXeWRvG73/3ODA0NNRcvXmympKRYj/z8fOueF154wQwNDTU//fRTc9OmTebYsWPN+Ph4Mzs7uxFbXncqrsYyTc/q76pVq0xvb2/zL3/5i7lr1y7zww8/NAMCAswPPvjAuseT+nvXXXeZzZo1M7/88kszOTnZ/PTTT82oqCjzscces+45X/ubk5Njrl+/3ly/fr0JmK+99pq5fv16a/VRTfp13333mc2bNzcXLlxorlu3zrzqqqvMHj16mKWlpY3VrWqdrr8lJSXmqFGjzObNm5sbNmxw+7urqKjI+gxP6W9VTl2NZZqe1d9PP/3U9PHxMf/5z3+au3btMqdNm2babDZz6dKl1mc0VH8VdjwAUOVj5syZ1j1lZWXmn/70JzMuLs602+3mFVdcYW7atKnxGl3HTg07ntbfL774wkxKSjLtdrvZqVMn85///KfbdU/qb3Z2tvnQQw+ZLVq0MP38/Mw2bdqYf/jDH9x+AJ6v/f3++++r/LN61113maZZs34VFBSYDzzwgBkREWH6+/ubI0aMMA8cONAIvTmz0/U3OTm52r+7vv/+e+szPKW/Vakq7Hhaf9955x2zXbt2pp+fn9mjRw9z7ty5bp/RUP01TNM063asSERERKTpUM2OiIiIeDSFHREREfFoCjsiIiLi0RR2RERExKMp7IiIiIhHU9gRERERj6awIyIiIh5NYUdEREQ8msKOiDSKVq1a8cYbb9T4/sWLF2MYBpmZmfXWJhHxTNpBWURqZNCgQVx00UVnFVBO5+jRowQGBhIQEFCj+4uLizlx4gSxsbEYhlEnbThbixcv5sorryQjI4OwsLBGaYOInD3vxm6AiHgO0zRxOBx4e5/5r5bo6Oiz+mxfX1/i4uJq2zQRuYBpGktEzmjcuHEsWbKEN998E8MwMAyDffv2WVNL3377Lb1798Zut7N06VL27NnD9ddfT2xsLEFBQfTp04eFCxe6feap01iGYfCvf/2LG2+8kYCAANq3b8/nn39uXT91Guvdd98lLCyMb7/9ls6dOxMUFMQ111xDSkqK9Z7S0lIefPBBwsLCiIyM5PHHH+euu+7ihhtuqLav+/fvZ+TIkYSHhxMYGEjXrl356quv2LdvH1deeSUA4eHhGIbBuHHjAGfIe+mll2jTpg3+/v706NGD//3vf5XaPm/ePHr06IGfnx/9+vVj06ZNZ/xeETl3CjsickZvvvkm/fv359577yUlJYWUlBQSExOt64899hhTp05l27ZtdO/endzcXK699loWLlzI+vXrGTZsGCNHjuTAgQOn/Z5nnnmGMWPGsHHjRq699lpuu+02Tpw4Ue39+fn5vPLKK7z//vv88MMPHDhwgMmTJ1vXX3zxRT788ENmzpzJ8uXLyc7OZu7cuadtw/jx4ykqKuKHH35g06ZNvPjiiwQFBZGYmMgnn3wCwI4dO0hJSeHNN98E4I9//CMzZ85kxowZbNmyhYcffpjbb7+dJUuWuH32o48+yiuvvMLq1auJiYlh1KhRlJSUnPZ7RaQO1Pk56iLikQYOHGg+9NBDbq99//33JmDOnTv3jO/v0qWLOW3aNOt5y5Ytzddff916Dph//OMfree5ubmmYRjm119/7fZdGRkZpmma5syZM03A3L17t/Wet956y4yNjbWex8bGmi+//LL1vLS01GzRooV5/fXXV9vObt26mVOmTKny2qltcLXTz8/PXLFihdu9d999tzl27Fi3982ePdu6fvz4cdPf39/8+OOPz/i9InJuVLMjIuesd+/ebs/z8vJ45pln+PLLLzly5AilpaUUFBSccWSne/fu1q8DAwMJDg4mPT292vsDAgJo27at9Tw+Pt66Pysri7S0NPr27Wtdt9ls9OrVi7Kysmo/88EHH+R3v/sd8+fPZ/Dgwdx0001u7TrV1q1bKSwsZMiQIW6vFxcX07NnT7fX+vfvb/06IiKCjh07sm3btlp9r4jUnKaxROScBQYGuj1/9NFH+eSTT/jLX/7C0qVL2bBhA926daO4uPi0n+Pj4+P23DCM0waTqu43T1lgeurKrVOvn+qee+5h79693HHHHWzatInevXszbdq0au93tW/evHls2LDBemzdutWtbqc6rvad7feKSM0p7IhIjfj6+uJwOGp079KlSxk3bhw33ngj3bp1Iy4ujn379tVvA08RGhpKbGwsq1atsl5zOBysX7/+jO9NTEzkvvvu49NPP+WRRx7h7bffBpy/B67PcenSpQt2u50DBw7Qrl07t0fFuiaAlStXWr/OyMhg586ddOrU6YzfKyLnRtNYIlIjrVq14qeffmLfvn0EBQURERFR7b3t2rXj008/ZeTIkRiGwVNPPXXaEZr6MmHCBKZOnUq7du3o1KkT06ZNIyMj47T79EycOJHhw4fToUMHMjIyWLRoEZ07dwagZcuWGIbBl19+ybXXXou/vz/BwcFMnjyZhx9+mLKyMi677DKys7NZsWIFQUFB3HXXXdZnP/vss0RGRhIbG8sf/vAHoqKirJVhp/teETk3GtkRkRqZPHkyNpuNLl26EB0dfdr6m9dff53w8HAGDBjAyJEjGTZsGBdffHEDttbp8ccfZ+zYsdx5553079+foKAghg0bhp+fX7XvcTgcjB8/ns6dO3PNNdfQsWNH/va3vwHQrFkznnnmGX7/+98TGxvLAw88AMBzzz3H008/zdSpU+ncuTPDhg3jiy++oHXr1m6f/cILL/DQQw/Rq1cvUlJS+Pzzz91Gi6r7XhE5N9pBWUQuGGVlZXTu3JkxY8bw3HPPNdj3audlkcalaSwR8Vj79+9n/vz5DBw4kKKiIqZPn05ycjK33nprYzdNRBqQprFExGN5eXnx7rvv0qdPHy699FI2bdrEwoULVQsjcoHRNJaIiIh4NI3siIiIiEdT2BERERGPprAjIiIiHk1hR0RERDyawo6IiIh4NIUdERER8WgKOyIiIuLRFHZERETEo/0/VHmbpiid61kAAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 准备优化器和模型\n",
    "vocab_size = len(dataset.token2id)\n",
    "hidden_size = 32\n",
    "n_layers = 1\n",
    "dropout = 0\n",
    "n_tags = len(dataset.label2id)\n",
    "batch_size = 128\n",
    "epochs = 20\n",
    "learning_rate = 1e-2\n",
    "\n",
    "lstm_crf = LSTM_CRF(vocab_size, hidden_size, n_layers, dropout, n_tags)\n",
    "train(lstm_crf, batch_size, epochs, learning_rate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "8a9138a5",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-04-19T05:42:19.195106Z",
     "start_time": "2025-04-19T05:42:07.669703Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "precision = 0.591320592039086, recall = 0.49058178350023846, f1 = 0.5362611585326122\n",
      "precision = 0.49538461538461537, recall = 0.39436619718309857, f1 = 0.43914081145584727\n"
     ]
    }
   ],
   "source": [
    "evaluate(train_X, train_Y, lstm_crf, batch_size)\n",
    "evaluate(test_X, test_Y, lstm_crf, batch_size)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7b14d688",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d72e6044",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bff4b39f",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.4"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": false,
   "sideBar": false,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": true,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
